package com.sdy.sd;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import androidx.core.graphics.ColorUtils;
import java.util.ArrayList;

class Coord {
    float x;
    float y;

    public Coord() {x = 0f; y = 0f;}
    public Coord(int x1, int y1) {x = x1; y = y1;}
}

class Particle {
    public int x;
    public int y;
    public Coord[] coordLast;

    int angle;// = ParticleFW.rand(0, 360);
    int speed;// = rand(((self.partSpeed - self.partSpeedVariance) <= 0) ? 1 : self.partSpeed - self.partSpeedVariance, (self.partSpeed + self.partSpeedVariance)),
    float friction;//: 1 - self.partFriction / 100,
    float gravity;//: self.partGravity / 2,   !!!!! convert to float and then div!!!!!!!!

    int hue;//: rand(hue-self.hueVariance, hue+self.hueVariance),
    int brightness;//: rand(50, 80),
    float alpha;// = ParticleFW.rand(40,100) / 100f;
    float decay;//: rand(10, 50) / 1000,
    float wind;//: (rand(0, self.partWind) - (self.partWind / 2)) / 25,
    float lineWidth;//: self.lineWidth

    public Particle(ParticleFW self, int x1, int y1, int hue1) {
        x = x1;
        y = y1;
        coordLast = new Coord[3];
        for(int i = 0; i < 3; ++i) {
            coordLast[i] = new Coord();
            coordLast[i].x = x1; coordLast[i].y = y1;
        }

        angle = self.rand(0, 360);
        speed = self.rand(((self.partSpeed - self.partSpeedVariance) <= 0) ? 1 : self.partSpeed - self.partSpeedVariance, (self.partSpeed + self.partSpeedVariance));
        friction = 1f - self.partFriction / 100f;
        gravity = self.partGravity / 2f;
        hue = self.rand(hue1 - self.hueVariance, hue1 + self.hueVariance);
        brightness = self.rand(50, 80);
        alpha = self.rand(40,100) / 100f;
        decay = self.rand(10, 50) / 1000f;
        wind = (self.rand(0, self.partWind) - (self.partWind / 2f)) / 25f;
        lineWidth = self.lineWidth;
    }
}

class Firework {
    double x;//: startX,
    double y;//: startY,
    int startX;//: startX,
    int startY;//: startY,
    boolean hitX = false;
    boolean hitY = false;
    Coord[] coordLast;

    int targetX;//: targetX,
    int targetY;//: targetY,
    double speed;//: self.fworkSpeed,
    double angle;//: Math.atan2(targetY - startY, targetX - startX),
    double shockwaveAngle;//: Math.atan2(targetY - startY, targetX - startX) + (90 * (Math.PI / 180)),
    float acceleration;//: self.fworkAccel / 100,
    int hue;//: self.currentHue,
    int brightness;// = ParticleFW.rand(50, 80);
    float alpha;// = ParticleFW.rand(50, 100) / 100f;
    float lineWidth;//: self.lineWidth

    public Firework(ParticleFW self, int startX1, int startY1, int targetX1, int targetY1) {
        x = startX1;
        y = startY1;
        startX = startX1;
        startY = startY1;
        coordLast = new Coord[3];
        for(int i = 0; i < 3; ++i) {
            coordLast[i] = new Coord();
            coordLast[i].x = startX1;coordLast[i].y = startY1;
        }

        targetX = targetX1;
        targetY = targetY1;
        speed = self.fworkSpeed;
        angle = Math.atan2(targetY - startY, targetX - startX);
        shockwaveAngle = Math.atan2(targetY - startY, targetX - startX) + (90f * (Math.PI / 180f));
        acceleration = self.fworkAccel / 100f;
        hue = self.currentHue;
        brightness = ParticleFW.rand(50, 80);
        alpha = ParticleFW.rand(50, 100) / 100f;
        lineWidth = self.lineWidth;
    }
}

//particle firework
public class ParticleFW extends View {
    public static final String TAG = "ParticleFW";
    //Canvas canvas = null;
    Canvas mCanvas;
    Bitmap mBmp;

    ParticleFW self = this;
    private final Paint mPaint = new Paint();
    Xfermode mXferMode_DSTOUT = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
    public int mStatus = ST_STOPPED;
    private final Path mPath = new Path();

    static final int ST_STOPPED     = 0;
    static final int ST_PLAYING     = 1;

    int cw;
    int ch;

    ArrayList<Particle> particles = new ArrayList<Particle>();
    int partCount = 150;
    ArrayList<Firework> fireworks = new ArrayList<Firework>();

    int currentHue = 30;
    int partSpeed = 5;
    int partSpeedVariance = 10;
    int partWind = 50;
    int partFriction = 5;
    int partGravity = 1;
    int hueMin = 0;
    int hueMax = 360;
    int fworkSpeed = 4;
    int fworkAccel = 10;
    int hueVariance = 30;
    int flickerDensity = 25;
    boolean showShockwave = true;
    boolean showTarget = false;
    int clearAlpha = 25;
    float lineWidth;



    float dp2pix(float dpSize) {
        DisplayMetrics dm = getResources().getDisplayMetrics() ;
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpSize, dm);
    }
    //h:Hue [0 .. 360)  s:Saturation [0...1]    l:Lightness [0...1]       a:alpha 0-255
    static int hsla2argb(int h_i0_359, float s_f0_1, float l_f0_1, int a_i0_255) {
        float[] hsl = new float[3];
        hsl[0] = h_i0_359;
        hsl[1] = s_f0_1;
        hsl[2] = l_f0_1;
        return (ColorUtils.HSLToColor(hsl) | ((a_i0_255 << 24)));
    }
    static public int rand(int rMi, int rMa) { //gen integer in range:[rMi, rMa]
        return (int)((Math.random() * (rMa - rMi + 1)) + rMi);
    }
    private boolean hitTest(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
        return !(x1 + w1 < x2 || x2 + w2 < x1 || y1 + h1 < y2 || y2 + h2 < y1);
    };

    private boolean init() {
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        DisplayMetrics dm = getResources().getDisplayMetrics();
        self.cw = dm.widthPixels;
        self.ch = dm.heightPixels;
        mBmp = Bitmap.createBitmap(self.cw, self.ch, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBmp);

        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        self.lineWidth = dp2pix(1f) * 1.5f;
        //Log.e(TAG, "lineWidth:" + self.lineWidth); //2.75

        return true;
    };

    void createParticles(int x, int y, int hue) {
        int countdown = self.partCount;
        while(0 != countdown--) {
            Particle newParticle = new Particle(self, x, y, hue);
            self.particles.add(newParticle);
        }
    };

    boolean updateParticles() {
        int i = self.particles.size();
        boolean ret = (i > 0);
        while(0 != i--) {
            Particle p = self.particles.get(i);
            double radians = p.angle * Math.PI / 180f;
            double vx = Math.cos(radians) * p.speed;
            double vy = Math.sin(radians) * p.speed;
            p.speed *= p.friction;

            p.coordLast[2].x = p.coordLast[1].x;
            p.coordLast[2].y = p.coordLast[1].y;
            p.coordLast[1].x = p.coordLast[0].x;
            p.coordLast[1].y = p.coordLast[0].y;
            p.coordLast[0].x = p.x;
            p.coordLast[0].y = p.y;

            p.x += vx;
            p.y += vy;
            p.y += p.gravity;

            p.angle += p.wind;
            p.alpha -= p.decay;

            //if(!hitTest(0,0,self.cw, self.ch,p.x - p.radius, p.y-p.radius, p.radius*2, p.radius*2) || p.alpha < .05) {
            if(!hitTest(0,0, self.cw, self.ch, p.x, p.y, 0, 0) || p.alpha < .05) {
                //self.particles.splice(i, 1);
                self.particles.remove(i);
            }
        };
        return ret;
    };

    void drawParticles(Canvas canvas) {
        int i = self.particles.size();
        while(0 != i--) {
            Particle p = self.particles.get(i);
            int coordRand = (rand(1,3) - 1);
            mPath.reset();

            mPath.moveTo(Math.round(p.coordLast[coordRand].x), Math.round(p.coordLast[coordRand].y));
            mPath.lineTo(Math.round(p.x), Math.round(p.y));
            mPath.close();

            float s = 1f;
            float l = p.brightness / 100f; //80?
            int a = (int)(p.alpha * 256);
            mPaint.setColor(hsla2argb(p.hue, s, l, a));
            canvas.drawPath(mPath, mPaint);

            if(self.flickerDensity > 0) {
                int inverseDensity = 50 - self.flickerDensity;
                if(rand(0, inverseDensity) == inverseDensity) {
                    mPath.reset();

                    float radius = rand((int)p.lineWidth,(int)p.lineWidth + 4) / 2f;
                    float left = Math.round(p.x - radius);
                    float top = Math.round(p.y - radius);
                    float right = Math.round(p.x + radius);
                    float bottom = Math.round(p.y + radius);
                    mPath.arcTo(left, top, right, bottom, 0, 359, false);

                    mPath.close();

                    int randAlpha = rand(50,100) * 255 / 100;
                    s = 1f;
                    l = p.brightness / 100f; //80?
                    mPaint.setColor(hsla2argb(p.hue, s, l, randAlpha));
                    mPaint.setStyle(Paint.Style.FILL);

                    canvas.drawPath(mPath, mPaint);
                    mPaint.setStyle(Paint.Style.STROKE);
                }
            }
        };
    };

    void createFireworks(int startX, int startY, int targetX, int targetY) {
        Firework newFirework = new Firework(this, startX, startY, targetX, targetY);
        self.fireworks.add(newFirework);
    };


    boolean updateFireworks() {
        int i = self.fireworks.size();
        boolean ret = (i > 0);
        while(0 != (i--)) {
            Firework f = self.fireworks.get(i);
            mPaint.setStrokeWidth(f.lineWidth);

            double vx = Math.cos(f.angle) * f.speed;
            double vy = Math.sin(f.angle) * f.speed;
            f.speed *= (1f + f.acceleration);
            f.coordLast[2].x = f.coordLast[1].x;
            f.coordLast[2].y = f.coordLast[1].y;
            f.coordLast[1].x = f.coordLast[0].x;
            f.coordLast[1].y = f.coordLast[0].y;
            f.coordLast[0].x = (float) f.x;
            f.coordLast[0].y = (float) f.y;

            if(f.startX >= f.targetX) {
                if(f.x + vx <= f.targetX) {
                    f.x = f.targetX;
                    f.hitX = true;
                } else {
                    f.x += vx;
                }
            } else {
                if(f.x + vx >= f.targetX) {
                    f.x = f.targetX;
                    f.hitX = true;
                } else {
                    f.x += vx;
                }
            }

            if(f.startY >= f.targetY) {
                if(f.y + vy <= f.targetY) {
                    f.y = f.targetY;
                    f.hitY = true;
                } else {
                    f.y += vy;
                }
            } else {
                if(f.y + vy >= f.targetY) {
                    f.y = f.targetY;
                    f.hitY = true;
                } else {
                    f.y += vy;
                }
            }

            if(f.hitX && f.hitY) {
                self.createParticles(f.targetX, f.targetY, f.hue);
                assert (self.fireworks.size() > 0);
                self.fireworks.remove(i);
            }
        };
        return ret;
    };

    void drawFireworks(Canvas canvas) {
        int i = self.fireworks.size();
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));

        while(0 != (i--)) {
            Firework f = self.fireworks.get(i);

            mPaint.setStrokeWidth(f.lineWidth);
            int coordRand = (rand(1, 3) - 1);

            mPath.reset();
            mPath.moveTo(f.coordLast[coordRand].x, f.coordLast[coordRand].y);
            mPath.lineTo((float) f.x, (float) f.y);
            mPath.close();

            int h = f.hue;
            float s = 1f;
            float l = f.brightness / 100f; //80?
            int a = (int)(f.alpha * 255);
            mPaint.setColor(hsla2argb(h, s, l, a));
            canvas.drawPath(mPath, mPaint);

            if(self.showTarget) {
                canvas.save();
                mPath.reset();

                int radius = rand(1,8);
                float left = Math.round(f.targetX) - radius;
                float top = Math.round(f.targetY) - radius;
                float right = Math.round(f.targetX) + radius;
                float bottom = Math.round(f.targetY) + radius;
                mPath.arcTo(left, top, right, bottom, 0, -360, false);
                mPath.close();

                mPaint.setStrokeWidth(dp2pix(1));
                canvas.drawPath(mPath, mPaint);

                canvas.restore();
            }

            if(self.showShockwave) {
                canvas.save();
                canvas.translate(Math.round(f.x), Math.round(f.y));
                canvas.rotate((float) f.shockwaveAngle * 180f / (float) Math.PI);

                mPath.reset();
                //float radius = (float) (1 * (f.speed / 5d));
                float radius = (float) (Math.pow(f.speed, 0.25) * 2d);
                float left = 0 - radius;
                float top = 0 - radius;
                float right = 0 + radius;
                float bottom = 0 + radius;
                RectF rf = new RectF(left, top, right, bottom);
                mPath.arcTo(rf, 0, -180, false);

                s = 1f;
                l = f.brightness / 100f / 4; //80?
                a = (int)(rand(25, 60)  * 255f / 100f);
                mPaint.setColor(hsla2argb(f.hue, s, l, a));
                mPaint.setStrokeWidth(f.lineWidth);
                canvas.drawPath(mPath, mPaint);

                canvas.restore();
            }
        };
    };

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        DisplayMetrics dm = getResources().getDisplayMetrics();
        cw = dm.widthPixels;
        ch = dm.heightPixels;
        super.onSizeChanged(w, h, oldw, oldh);
    }

    void startNewFW(int fromX, int fromY, int tarX, int tarY ) {
        self.currentHue = rand(self.hueMin, self.hueMax);
        //Log.e(TAG, "from:[" + self.cw / 2 + ", " + self.ch + "]");
        self.createFireworks(fromX, fromY, tarX, tarY);
    }

    //return true: need refresh view
    public boolean update() {
        boolean fRet = self.updateFireworks();
        boolean pRet = self.updateParticles();
        return fRet || pRet;
    }

    public ParticleFW(Context context) {
        super(context);
        self.init();
    }
    public ParticleFW(Context context, AttributeSet attrs) {
        super(context, attrs);
        self.init();
    }

    protected void onDraw(Canvas canvas) {
        if(ST_PLAYING != mStatus) return;

        mPaint.setXfermode(mXferMode_DSTOUT);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.argb((int)(self.clearAlpha * 255f / 100f), 0, 0, 0));
        mCanvas.drawPaint(mPaint);
        mPaint.setStyle(Paint.Style.STROKE);

        self.drawFireworks(mCanvas);
        self.drawParticles(mCanvas);
        canvas.drawBitmap(mBmp, 0, 0, null);
    }
}
